package com.fadis.cmd;

import com.fadis.FadisException;
import com.fadis.FadisLog;
import com.fadis.data.base.StringZSet;
import com.fadis.data.zset.ZSet;
import com.fadis.data.zset.ZSkipListNode;
import com.fadis.db.FadisDB;
import com.fadis.protocol.BulkString;
import com.fadis.protocol.LongNumber;
import com.fadis.protocol.RespArray;
import com.fadis.protocol.SimpleString;

import java.util.ArrayList;
import java.util.List;

public class CmdZSet {

    /**
     *
     * ZADD key [NX|XX|none] [CH] [INCR] score member [score member ...]
     *
     * 可以不存在，新建
     * member不存在，插入
     * member存在，更新score，重新插入以保证排序
     *
     * ZADD 命令在key后面分数/成员（score/member）对前面支持一些参数，他们是：
     * XX: 仅仅更新存在的成员，不添加新成员。
     * NX: 仅仅添加新成员, 不更新存在的成员。
     * CH: 修改返回值为发生变化的成员总数，原始是返回新添加成员的总数 (CH 是 changed 的意思)。更改的元素是新添加的成员，已经存在的成员更新分数。 所以在命令中指定的成员有相同的分数将不被计算在内。注：在通常情况下，ZADD返回值只计算新添加成员的数量。
     * INCR: 当ZADD指定这个选项时，更新的操作就等同ZINCRBY命令，对成员的分数进行递增操作。
     *
     *
     * @return 无ch选项-返回新增数量，有ch选项-返回修改数量
     *
     * 问题：如果命令里有同样的member呢？算1次create +  1次update？
     */
    public static final class zadd implements CmdHandler{

        @Override
        public String run(FadisActionFrame frame) throws FadisException {
            String key = frame.getArg(1);
            int currentIndex = 2;
            // ZADD key [NX|XX|none] [CH] [INCR] score member [score member ...]

            ExistOption existOption = ExistOption.IGNORE;
            String maybeExistsOption = frame.getArg(currentIndex);
            if(maybeExistsOption.equalsIgnoreCase("nx")
                    || maybeExistsOption.equalsIgnoreCase("xx")
                    || maybeExistsOption.equalsIgnoreCase("none")){
                currentIndex++;
                if("nx".equalsIgnoreCase(maybeExistsOption)){
                    existOption = ExistOption.NX;
                }else if("xx".equalsIgnoreCase(maybeExistsOption)){
                    existOption = ExistOption.XX;
                }else if("none".equalsIgnoreCase(maybeExistsOption)){
                    existOption = ExistOption.IGNORE;
                }
            }

            boolean returnChange = false;
            String maybeCh = frame.getArg(currentIndex);
            if("ch".equalsIgnoreCase(maybeCh)){
                currentIndex++;
                returnChange = true;
            }

            boolean updateByIncr = false;
            String maybeIncre = frame.getArg(currentIndex);
            if("incre".equalsIgnoreCase(maybeIncre)){
                currentIndex++;
                updateByIncr = true;
            }

            StringZSet set = FadisDB.tryToGetOrCreateZSet( key, true);
            int createCount = 0;
            int updateCount = 0;
            int ignoreCount = 0;
            String scoreStr = null;
            for (int i = currentIndex; i < frame.getArgsCount(); i++) {
                if(scoreStr == null){
                    scoreStr = frame.getArg(i);
                }else{
                    String member = frame.getArg(i);
                    Double score = Double.parseDouble(scoreStr);
                    scoreStr = null;
                    int r = set.add(score, member, existOption, updateByIncr);
                    //-1忽略， 0新增，1更新
                    if(r == -1) ignoreCount++;
                    else if(r == 0) createCount++;
                    else if(r == 1) updateCount++;
                }
            }


            int rv = returnChange ? updateCount : createCount;
            return LongNumber.encodeInt(rv);
        }
    }
    // ZSCORE key member
    // member 成员的 score 值， key无或member无返回null
    public static final class zscore implements CmdHandler{

        @Override
        public String run(FadisActionFrame frame) throws FadisException {
            String key = frame.getArg(1);
            String member = frame.getArg(2);
            StringZSet set = FadisDB.tryToGetOrCreateZSet( key, false);
            if(set == null) return BulkString.encodedNull();
            return SimpleString.encodedSimpleString(set.score(member) + "");
        }
    }

    // ZINCRBY key increment member
    // 返回新 score，key无或member无都新建一个score=0，再incr
    public static final class zincrby implements CmdHandler{

        @Override
        public String run(FadisActionFrame frame) throws FadisException {
            String key = frame.getArg(1);
            String incrementStr = frame.getArg(2);
            String member = frame.getArg(3);
            double increment = Double.parseDouble(incrementStr);

            StringZSet set = FadisDB.tryToGetOrCreateZSet( key, true);
            double r = set.incrby(member, increment);

            return SimpleString.encodedSimpleString(r + "");
        }
    }
    // ZCARD key
    // 返回有序集合的元素数量（基数），或者0
    public static final class zcard implements CmdHandler{

        @Override
        public String run(FadisActionFrame frame) throws FadisException {
            String key = frame.getArg(1);
            StringZSet set = FadisDB.tryToGetOrCreateZSet( key, false);
            long r = set == null ? 0 : set.card();
            return LongNumber.encodeLong(r);
        }
    }
    // ZCOUNT key min max
    // min = (5  开区间
    // min = [5 闭区间，默认
    // min = -inf 负无限  max = +inf 正无限
    // 返回score 值在 min 和 max 之间的成员的数量（include）
    public static final class zcount implements CmdHandler{

        @Override
        public String run(FadisActionFrame frame) throws FadisException {
            String key = frame.getArg(1);
            String minStr = frame.getArg(2).toLowerCase();
            String maxStr = frame.getArg(3).toLowerCase();
            ZSetScoreRange range = ZSetScoreRange.from(minStr, maxStr);

            StringZSet set = FadisDB.tryToGetOrCreateZSet( key, false);
            long r = set == null ? 0 : set.count(range.min, range.minInclude, range.max, range.maxInclude);

            return LongNumber.encodeLong(r);
        }
    }

    // ZRANGE key start stop [WITHSCORES]   -- 根据排名
    // 列表，[obj, obj] 或者 [obj, score, obj, score] -- 无返回null
    public static final class zrange implements CmdHandler{

        @Override
        public String run(FadisActionFrame frame) throws FadisException {
            String key = frame.getArg(1);
            String startStr = frame.getArg(2);
            String stopStr = frame.getArg(3);
            String withScoresStr = frame.getArg(4);

            int start = Integer.parseInt(startStr);
            int stop = Integer.parseInt(stopStr);
            boolean withScores = "withscores".equalsIgnoreCase(withScoresStr);

            StringZSet set = FadisDB.tryToGetOrCreateZSet( key, false);
            if(set == null) return BulkString.encodedNull();
            List<ZSkipListNode<String>> list = set.range(start, stop);
            if(list == null) return BulkString.encodedNull();
            if(list.isEmpty()) return RespArray.encodeList(new ArrayList<>());

            List<String> result = new ArrayList<>();
            for (int i = 0; i < list.size(); i++) {
                result.add(list.get(i).obj);
                if(withScores) result.add(list.get(i).score + "");
            }

            return RespArray.encodeList(result);
        }
    }

    // ZREVRANGE key start stop [WITHSCORES]  -- 根据排名
    // 列表，[obj, obj] 或者 [obj, score, obj, score] -- 无返回null
    public static final class zrevrange implements CmdHandler{

        @Override
        public String run(FadisActionFrame frame) throws FadisException {
            String key = frame.getArg(1);
            String startStr = frame.getArg(2);
            String stopStr = frame.getArg(3);
            String withScoresStr = frame.getArg(4);

            int start = Integer.parseInt(startStr);
            int stop = Integer.parseInt(stopStr);
            boolean withScores = "withscores".equalsIgnoreCase(withScoresStr);

            StringZSet set = FadisDB.tryToGetOrCreateZSet( key, false);
            if(set == null) return BulkString.encodedNull();
            List<ZSkipListNode<String>> list = set.revrange(start, stop);
            if(list == null) return BulkString.encodedNull();
            if(list.isEmpty()) return RespArray.encodeList(new ArrayList<>());

            List<String> result = new ArrayList<>();
            for (int i = 0; i < list.size(); i++) {
                result.add(list.get(i).obj);
                if(withScores) result.add(list.get(i).score + "");
            }

            return RespArray.encodeList(result);
        }
    }
    // ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]
    // ZREVRANGEBYSCORE key max min [WITHSCORES] [LIMIT offset count]
    // min 和 max 可以是 -inf 和 +inf
    // 5是闭区间  (5是开区间
    // 返回列表，[obj, obj] 或者 [obj, score, obj, score] -- 或空集合，永远不会返回null
    public static final class zrangebyscore implements CmdHandler{

        @Override
        public String run(FadisActionFrame frame) throws FadisException {
            String key = frame.getArg(1);
            boolean reverse = frame.getArg(0).toLowerCase().contains("rev");
            CmdUtils.ZRangeByScoreParams param = CmdUtils.ZRangeByScoreParams.from(frame,reverse);
            System.out.println("range by score param: " + param);

            StringZSet set = FadisDB.tryToGetOrCreateZSet( key, false);
            if(set == null) return RespArray.encodeList(new ArrayList<>());

            List<ZSkipListNode<String>> list = null;
            if(reverse) list = set.revRangeByScore(param.range.min, param.range.minInclude, param.range.max, param.range.maxInclude);
            else list = set.rangeByScore(param.range.min, param.range.minInclude, param.range.max, param.range.maxInclude);

//            if(list == null) return BulkString.encodedNull();
            if(list == null || list.isEmpty()) return RespArray.encodeList(new ArrayList<>());

            List<String> result = new ArrayList<>();
            for (int i = 0; i < list.size(); i++) {
                if(i < param.offset) continue;
                if(param.limit && result.size() > param.count * (param.withScores ? 2 : 1)) break;
                result.add(list.get(i).obj);
                if(param.withScores) result.add(list.get(i).score + "");
            }
            return RespArray.encodeList(result);
        }
    }

    // ZRANK key member
    // 排名，从0开始，无返回null
    public static final class zrank implements CmdHandler{

        @Override
        public String run(FadisActionFrame frame) throws FadisException {
            String key = frame.getArg(1);
            String member = frame.getArg(2);
            StringZSet set = FadisDB.tryToGetOrCreateZSet( key, false);
            if(set == null) return BulkString.encodedNull();
            long rank = set.rank(member);
            if(rank == -1) return BulkString.encodedNull();
            return LongNumber.encodeLong(rank);
        }
    }

    // ZREVRANK key member
    // 倒序排名，从0开始，无返回null
    public static final class zrevrank implements CmdHandler{

        @Override
        public String run(FadisActionFrame frame) throws FadisException {
            String key = frame.getArg(1);
            String member = frame.getArg(2);
            StringZSet set = FadisDB.tryToGetOrCreateZSet( key, false);
            if(set == null) return BulkString.encodedNull();
            long rank = set.revRank(member);
            if(rank == -1) return BulkString.encodedNull();
            return LongNumber.encodeLong(rank);
        }
    }
    // ZREM key member [member …]
    // 返回被成功移除的成员的数量
    public static final class zrem implements CmdHandler{

        @Override
        public String run(FadisActionFrame frame) throws FadisException {
            String key = frame.getArg(1);
            List<String> members = new ArrayList<>();
            for (int i = 2; i < frame.getArgsCount(); i++) {
                members.add(frame.getArg(i));
            }

            StringZSet set = FadisDB.tryToGetOrCreateZSet( key, false);
            if(set == null) return LongNumber.encodeInt(0);
            int deleteCount = 0;
            for (int i = 0; i < members.size(); i++) {
                int r = set.rem(members.get(i));
                if(r != -1) deleteCount += 1;
            }

            return LongNumber.encodeInt(deleteCount);
        }
    }

    // ZREMRANGEBYRANK key start stop
    // 返回 被移除成员的数量
    public static final class zremrangebyrank implements CmdHandler{

        @Override
        public String run(FadisActionFrame frame) throws FadisException {
            String key = frame.getArg(1);
            String startStr = frame.getArg(2);
            String stopStr = frame.getArg(3);

            long start = Long.parseLong(startStr);
            long stop = Long.parseLong(stopStr);

            StringZSet set = FadisDB.tryToGetOrCreateZSet( key, false);
            if(set == null) return LongNumber.encodeInt(0);
            long r = set.remRangeByRank(start, stop);
            return LongNumber.encodeLong(r);
        }
    }
    // ZREMRANGEBYSCORE key min max
    // 返回被移除成员的数量。
    public static final class zremrangebyscore implements CmdHandler{

        @Override
        public String run(FadisActionFrame frame) throws FadisException {
            String key = frame.getArg(1);
            String minStr = frame.getArg(2);
            String maxStr = frame.getArg(3);
            ZSetScoreRange range = ZSetScoreRange.from(minStr, maxStr);

            StringZSet set = FadisDB.tryToGetOrCreateZSet( key, false);
            if(set == null) return LongNumber.encodeInt(0);
            long r = set.remRangeByScore(range.min, range.minInclude, range.max, range.maxInclude);
            return LongNumber.encodeLong(r);
        }
    }


    // ZINTERSTORE destination numkeys key [key …] [WEIGHTS weight [weight …]] [AGGREGATE SUM|MIN|MAX]
    // ZUNIONSTORE destination numkeys key [key …] [WEIGHTS weight [weight …]] [AGGREGATE SUM|MIN|MAX]
    // 返回保存到 destination 的结果集的基数
    // numkeys: 表示有几个key
    // WEIGHTS: 为每个zset分别指定一个乘法因子(默认1), score 值在传递给聚合函数(aggregation function)之前都要先乘以该有序集的因子
    // AGGREGATE: sum--score相加  min--score取最小值 max--score取最大值
    public static final class zinterstore implements CmdHandler{

        @Override
        public String run(FadisActionFrame frame) throws FadisException {
            String cmd = "";
            if(frame.getArg(0).toLowerCase().contains("inter")) cmd = "inter";
            else cmd = "union";

            String destination = frame.getArg(1);
            String numkeysStr = frame.getArg(2);
            int numkeys = Integer.parseInt(numkeysStr);

            int currentIndex = 3;
            List<String> keys = new ArrayList<>();
            for (int i = 3; i < 3+numkeys; i++) {
                keys.add(frame.getArg(i));
                currentIndex = i;
            }

            List<Double> weight = new ArrayList<>();
            String aggr = "sum";
            boolean inWeight = false;
            boolean inAggr = false;

            for (int i = currentIndex+1; i < frame.getArgsCount(); i++) {
                String s = frame.getArg(i);
                if(s.equalsIgnoreCase("weights")){
                    inWeight = true;
                    inAggr = false;

                }else if(s.equalsIgnoreCase("AGGREGATE")){
                    inAggr = true;
                    inWeight = false;
                }else{
                    if(inWeight){
                        double w = Double.parseDouble(s);
                        weight.add(w);
                    }else if(inAggr){
                        aggr = s.toLowerCase();
                    }
                }
            }

            List<StringZSet> sets = new ArrayList<>();
            for (int i = 0; i < keys.size(); i++) {
                StringZSet set = FadisDB.tryToGetOrCreateZSet( keys.get(i), false);
                if(set != null) sets.add(set);
            }

            FadisLog.log("集合：" + cmd + ", " + aggr + ", " + weight);

            StringZSet r = null;
            if("inter".equals(cmd)) r = ZSet.inter(sets, weight, aggr);
            else if("union".equals(cmd)) r = ZSet.union(sets, weight, aggr);

            if(r == null || r.card() == 0){
                FadisDB.remove(destination);
            }else{
                FadisDB.put(destination, r);
            }

            return LongNumber.encodeLong(r == null ? 0 : r.card());
        }
    }


    // ZRANGEBYLEX key min max [LIMIT offset count]
    /*
    如何指定范围区间
合法的 min 和 max 参数必须包含 ( 或者 [ ， 其中 ( 表示开区间（指定的值不会被包含在范围之内）， 而 [ 则表示闭区间（指定的值会被包含在范围之内）。

特殊值 + 和 - 在 min 参数以及 max 参数中具有特殊的意义， 其中 + 表示正无限， 而 - 表示负无限。 因此， 向一个所有成员的分值都相同的有序集合发送命令 ZRANGEBYLEX <zset> - + ， 命令将返回有序集合中的所有元素
     */
    public static final class zrangebylex implements CmdHandler{

        @Override
        public String run(FadisActionFrame frame) throws FadisException {
            String key = frame.getArg(1);
            String minStr = frame.getArg(2);
            String maxStr = frame.getArg(3);
            boolean minInclude = true;
            boolean maxInclude = true;
            if(minStr.startsWith("(")){
                minInclude = false;
                minStr = minStr.substring(1);
            }else if(minStr.startsWith("[")){
                minInclude = true;
                minStr = minStr.substring(1);
            }

            if(maxStr.startsWith("(")){
                maxInclude = false;
                maxStr = maxStr.substring(1);
            }else if(maxStr.startsWith("[")){
                maxInclude = true;
                maxStr = maxStr.substring(1);
            }

            int currentIndex = 4;
            String limitStr = frame.getArg(currentIndex);
            boolean limit = "limit".equalsIgnoreCase(limitStr);
            long offset = 0;
            long count = -1;

            if(limit){
                currentIndex += 1;
                String offsetStr = frame.getArg(currentIndex);
                String countStr = frame.getArg(currentIndex + 1);
                offset = Long.parseLong(offsetStr);
                count = Long.parseLong(countStr);
            }

            ZSet set = FadisDB.tryToGetOrCreateZSet( key, false);
            if(set == null) return RespArray.encodeList(new ArrayList<>());
            if(count == -1) count = set.card();

            List<ZSkipListNode<String>> list = set.rangeByLex(minStr, minInclude, maxStr, maxInclude);
            if(list == null) list = new ArrayList<>();

            List<String> values = new ArrayList<>();
            for (int i = 0; i < list.size(); i++) {
                if(i < offset) continue;
                if(values.size() > count) break;
                values.add(list.get(i).obj);
            }
            return RespArray.encodeList(values);
        }
    }
    // ZLEXCOUNT key min max
    // 整数回复：指定范围内的元素数量。
    // ZLEXCOUNT myzset - +
    public static final class zlexcount implements CmdHandler{

        @Override
        public String run(FadisActionFrame frame) throws FadisException {
            String key = frame.getArg(1);
            String minStr = frame.getArg(2);
            String maxStr = frame.getArg(3);
            boolean minInclude = true;
            boolean maxInclude = true;
            if(minStr.startsWith("(")){
                minInclude = false;
                minStr = minStr.substring(1);
            }else if(minStr.startsWith("[")){
                minInclude = true;
                minStr = minStr.substring(1);
            }

            if(maxStr.startsWith("(")){
                maxInclude = false;
                maxStr = maxStr.substring(1);
            }else if(maxStr.startsWith("[")){
                maxInclude = true;
                maxStr = maxStr.substring(1);
            }

            ZSet set = FadisDB.tryToGetOrCreateZSet( key, false);
            if(set == null) return LongNumber.encodeInt(0);

            long r = set.lexCount(minStr, minInclude, maxStr, maxInclude);
            return LongNumber.encodeLong(r);
        }
    }
    // ZREMRANGEBYLEX key min max
    // 整数回复：被移除的元素数量。
    public static final class zremrangebylex implements CmdHandler{

        @Override
        public String run(FadisActionFrame frame) throws FadisException {
            String key = frame.getArg(1);
            String minStr = frame.getArg(2);
            String maxStr = frame.getArg(3);
            boolean minInclude = true;
            boolean maxInclude = true;
            if(minStr.startsWith("(")){
                minInclude = false;
                minStr = minStr.substring(1);
            }else if(minStr.startsWith("[")){
                minInclude = true;
                minStr = minStr.substring(1);
            }

            if(maxStr.startsWith("(")){
                maxInclude = false;
                maxStr = maxStr.substring(1);
            }else if(maxStr.startsWith("[")){
                maxInclude = true;
                maxStr = maxStr.substring(1);
            }

            ZSet set = FadisDB.tryToGetOrCreateZSet( key, false);
            if(set == null) return LongNumber.encodeInt(0);

            long r = set.deleteRangeByLex(minStr, minInclude, maxStr, maxInclude);
            return LongNumber.encodeLong(r);
        }
    }

}
